<!DOCTYPE html "-//W3C//DTD XHTML 1.0 Strict//EN" "http://www.w3.org/TR/xhtml1/DTD/xhtml1-strict.dtd">
<html xmlns="http://www.w3.org/1999/xhtml">
<head>
<meta http-equiv="content-type" content="text/html; charset=utf-8"/>
<title>SortStack()</title>

<style type="text/css">
html , body {width: 100%;height: 100%;font-family: Verdana, Arial, sans serif;
font-size: 12px;background-color: #ccf; margin: 10px;}
.block {width: 200px;height: 60px;text-align: center;border: 2px solid #00f;
background-color: #fff;margin: 10px;}
a:hover {text-decoration: underline overline;}
.odd {background-color: #eee;}
td {vertical-align: top;}
ul {list-style: none;}
th {background-color:#eee; margin:2px; font-weight:bold;}
.col-1 { width:300px;}
.test { width:450px; margin-top:8px;}
#story { width:450px; font-size: 12px;}
#adsense {margin:10px;}

</style>

<script id="api" src="http://maps.google.com/maps?file=api&amp;v=2&amp;hl=fi&amp;key=ABQIAAAAjU0EJWnWPMv7oQ-jjS7dYxSPW5CJgpdgO_s4yyMovOaVh_KvvhSfpvagV18eOyDWu7VytS6Bi1CWxw"
type="text/javascript"></script>

</head>

<body onunload="GUnload()">


<h2>Esa's Google Maps API experiences. SortStack() extension.</h2>
<table>
<tr><th class="col-1">Drag & drop sortable list</th><th class="col-2">by GDraggableObject</th><th class="col-3"></th></tr>
<tr><td>
<ul id="list">
<li><div  class="block odd"><h1> One </h1></div></li>
<li><div  class="block even"><h1> Two </h1></div></li>
<li><div  class="block odd"><h1> Three </h1></div></li>
<li><div  class="block even"><h1> Four </h1></div></li>
<li><div  class="block odd"><h1> Five </h1></div></li>
<li><div  class="block even"><h1> Six </h1></div></li>
<li><div  class="block odd"><h1> Seven </h1></div></li>
<li><div  class="block even"><h1> Eight </h1></div></li>
<li><div  class="block odd"><h1> Nine </h1></div></li>
<li><div  class="block even"><h1> Ten </h1></div></li>
<li><div  class="block odd"><h1> More </h1></div></li>
</ul>
</td>
<td>

<div id="story">
<div class="test">No map on this page. No, but still we load the Google Maps API for using the magnificent GDraggableObject. Did you try dragging the blocks on left.</div>
<div class="test">Sure the javascript of the leading draggable maps contains a proven and optimised mechanism for dragging dom blocks.</div>
<div class="test">GDraggableObject provides options and methods that are just made for making drag sortable lists. You don't have to load a huge general purpose javascript library for that purpose. </div>
<div class="test">Option {restrictX:true} makes the block draggable vertical only. left and .top properties return the position of the block. dragging() method is essential. It tells that object is under drag, so do not position that by code. <code>SortStack()</code> class was created using those gifts. Actually they been there for two years but I happened to be the first one trying them.</div>
<div class="test">When a block is made a GDraggableObject, its positioning is switched to 'absolute'. That destroys the page layout. They kind of vanish from the browser made layout. They must be wrapped in a container that keeps its size though its child elements vanish.</div>
<div class="test">The code of <code>SortStack()</code> finds the position of each draggable block <strong>before</strong> it is made draggable. The position is returned the first thing after the block is made draggable by moveTo() method.</div>
<div class="test">Finding blocks actual position is not as easy as it sounds. Dom elements do not have a property that would directly tell its position on page. offsetLeft and offsetTop tell you its position inside the parent element. So you have to also find those properties of the parent element and its parent element...</div>
<div class="test">I chose <a href="http://www.quirksmode.org/js/findpos.html">this</a> function to find the initial positions. I rely on Peter-Paul Koch's quirksmode.org. The function sums the offset properties of the chain. Still there are some browser specific errors caused by different ways to include borders, margins and paddings. So keep those in minimum. Also absolute positioning of parent element causes trouble.</div>
<div class="test">A stack of elements is made drag-sortable by <code>new SortStack(array);</code>. The parameter is an Array containing the dom nodes to be included in the stack.</div>
<div class="test">The array can be formed easily by <code>getElementsByTagName()</code>. See the function <code>makeBlockArray()</code> in source code.</div>
<div class="test">Drag sorting takes a lot of javascript computing and dom operations. Poor javascript performance of IE does not provide smooth dragging. That is why 'economy' option was written. <a href="sortstackie.htm">Test page</a>. By setting {economy:true}, the sorting is performed on 'drop' only (not on 'drag').</div>
<div class="test"><input type="button" onclick="reset()" value="Reset"/>
<code>SortStack.reset()</code> method returns to the original order.</div>
<div class="test"><strong>One size fits all.</strong> SortStack can handle only elements with equal height at this stage (v0.1). I will release a revision with capability to handle differently sized elements in a stack but it takes a lot of x-browser evaluation first.</div>
<div class="test">You can test what happens with differently sized elements by <a href="javascript:var sortStory=new SortStack(storyTest)">clicking this</a>. It makes paragraphs of this story a SortStack() instance. Luckily there is the powerful [Reset] button.</div>
<div class="test"><strong>Applications?</strong> Sure you immediately have an idea. Driving directions route order? Feel free to apply the code. The scary Apache license statement allows you to do anything with the code but not remove the statement. What? Yes, you can use, modify, improve, distribute or even include the code in paid projects but the Apache license keeps the copyright. So you cannot say it is your property, it is public property.</div>
<div class="test">
<a href="http://koti.mbnet.fi/ojalesa/exam/index.html">More experiments</a>
&nbsp;|&nbsp;
<a href="http://apitricks.blogspot.com/">Blog</a>
</div>
<div class="test"><span id="api-v"></span></div>
<div class="test"><span id="ss-v"></span></div>



</td>
<td>
<div id="adsense">
<script type="text/javascript"><!--
google_ad_client = "pub-3649938975494252";
/* 160x600, luotu 23.11.2008 */
google_ad_slot = "5318276372";
google_ad_width = 160;
google_ad_height = 600;
//-->
</script>
<script type="text/javascript"
src="http://pagead2.googlesyndication.com/pagead/show_ads.js">
</script>
</div>
</td>
</tr>
</table>


<script type="text/javascript">

/**
 * SortStack version 0.1 The initial release.
 */
var SORT_STACK_VERSION = '0.1';


/** A function that builds an array of child elements of a given element.
 *  @param id String
 *  @param opt_childTag Optional tagname string of child elements
 */
function makeBlockArray(id, opt_childTag){
  var tag = opt_childTag||'*';
  var children = document.getElementById(id).getElementsByTagName(tag);
  return children;
}

/** 
 *  We have to set opt_childTag parameter of myList to 'li'
 *  because 'list' contains 'div' and 'h1' elements too
 */
var myList = makeBlockArray("list","li");
var sortObject = new SortStack(myList);

var storyTest = makeBlockArray("story","div");

/** 
 *  [Reset] button
 */
 function reset(){
   sortObject.reset();
   if(typeof(sortStory)!='undefined')sortStory.reset();
 }

/**
*  Class SortStack()
 * Makes a drag sortable stack from an array of html elements
 * @Esa 2008
 * @param blocks Array of html elements
 * @param opt_options Object equal to GDraggableObject options plus {economy:boolean}
 * @method reset() Sets the stack to the initial order
 */ 
function SortStack(blocks, opt_options){
  var opts = opt_options||{};
  opts.restrictX = true;
  var drags = [];
  var safety = [];
  var positions = [];
  var zzTop = 0;
  var startPos = [];
  
  function findPosition(obj) {
    var curleft = curtop = 0;
    if (obj.offsetParent) {
      do { // trick from quirksmode
        curleft += obj.offsetLeft;
        curtop += obj.offsetTop;
      } while (obj = obj.offsetParent);
    } // not = typo
    return new GPoint(curleft,curtop);
  } // find original positions
  for(var i=0; i<blocks.length; i++){
    positions[i] = findPosition(blocks[i]);
  }  // create GDraggableObjects with event listeners
  for(var i=0; i<blocks.length; i++){
    drags[i] = new GDraggableObject(blocks[i], opts);
    safety[i] = drags[i]; // save original positions
    drags[i].moveTo(positions[i]);
    drags[i].label = blocks[i].innerHTML;
    drags[i].block = blocks[i];
    GEvent.addListener(drags[i], 'dragstart', function(){
      start();
      this.block.style.zIndex = ++zzTop;
    });
    if(!opts.economy){ // opts.economy prevents real time sorting. (IE mode)
      GEvent.addListener(drags[i], 'drag', function(){
        end();
      });
    }
    GEvent.addListener(drags[i], 'dragend', function(){
      end();
    });
  }
  function byTop(a,b){
    return a.top-b.top;
  } 
  function start(){// saving the start positions
    drags.sort(byTop);
    for(var i=0; i<drags.length; i++){
      startPos[i] = new GPoint(drags[i].left,drags[i].top);
    }
  }
  function end(){ // setting drop positions
    drags.sort(byTop);
    for(var i=0; i<drags.length; i++){
      if(!drags[i].dragging())drags[i].moveTo(startPos[i]);
    }
  }  // set to original positions
  this.reset = function(){
    for(var i=0; i<drags.length; i++){
      safety[i].moveTo(positions[i]);
    }
  }
}



/**
 * Api version display
 */
document.getElementById("api-v").innerHTML = 'Api v2.'+G_API_VERSION;
document.getElementById("ss-v").innerHTML = 'SortStack v'+SORT_STACK_VERSION;

</script>





<!-- Start of StatCounter Code -->
<script type="text/javascript">
var sc_project=4224096; 
var sc_invisible=1; 
var sc_partition=48; 
var sc_click_stat=1; 
var sc_security="28012877"; 
</script>

<script type="text/javascript" src="http://www.statcounter.com/counter/counter_xhtml.js"></script><noscript><div class="statcounter"><a title="site stats" class="statcounter" href="http://www.statcounter.com/"><img class="statcounter" src="http://c.statcounter.com/4224096/0/28012877/1/" alt="site stats" /></a></div></noscript>
<!-- End of StatCounter Code -->

</body>
</html>
